package com.symaster.common.swing.element;

import com.symaster.common.geom.PolygonPointEditing;
import com.symaster.common.geom.ShapeSpace;
import com.symaster.common.geom.entity.Node;
import com.symaster.common.geom.entity.Polygon;
import com.symaster.common.swing.KeyStatus;
import com.symaster.common.swing.controller.DragController;
import com.symaster.common.swing.util.SwingUtil;
import com.symaster.common.util.CommonUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.*;

/**
 * @author yinmiao
 * @version 2022-11-05 16:26
 */
public class Canvas2<T extends Node> extends KeyStatus implements Closeable {

    private final int w;
    private final int h;
    public static double allScale = 0.15;
    // private final CanvasDisplay canvasDisplay;
    private final ShapeSpace<T> shapeSpace;
    private final Map<Integer, Cache> cache;
    private final Set<DisplayListener> displayListeners;
    private final Set<Integer> selectList;
    private Integer mouseHoverId;
    private final List<Integer> sortList;
    private long lastTime = 0;

    public ShapeSpace<T> getShapeSpace() {
        return shapeSpace;
    }

    public Canvas2(int w, int h) {
        this.w = w;
        this.h = h;
        this.shapeSpace = new ShapeSpace<>(w, h, allScale);
        this.selectList = new HashSet<>();
        this.cache = new HashMap<>();
        this.displayListeners = new HashSet<>();
        this.sortList = new LinkedList<>();
        setLayout(null);
        _initListener();
        // this.canvasDisplay = new CanvasDisplay(this, 120);
        // this.canvasDisplay.start();
    }

    public Canvas2(int w, int h, double maxFps) {
        this.w = w;
        this.h = h;
        this.shapeSpace = new ShapeSpace<>(w, h, allScale);
        this.cache = new HashMap<>();
        this.selectList = new HashSet<>();
        this.displayListeners = new HashSet<>();
        this.sortList = new LinkedList<>();
        setLayout(null);
        _initListener();
        // this.canvasDisplay = new CanvasDisplay(this, maxFps);
        // this.canvasDisplay.start();
    }

    public void addPolygon(T node) {
        Integer id = node.getId();
        if (id == null) {
            node.setId(CommonUtil.getId());
        }
        shapeSpace.add(node, false);
        synchronized (sortList) {
            sortList.add(0, node.getId());
        }
    }

    public T getPolygon(Integer id) {
        return shapeSpace.findNode(id);
    }

    public int getW() {
        return w;
    }

    public int getH() {
        return h;
    }

    public void addDisplayListener(DisplayListener listener) {
        displayListeners.add(listener);
    }

    public void removeDisplayListener(DisplayListener listener) {
        displayListeners.remove(listener);
    }

    @Override
    public int getWidth() {
        return (int) Math.round(w * allScale);
    }

    @Override
    public int getHeight() {
        return (int) Math.round(h * allScale);
    }

    @Override
    public void paint(Graphics g) {
        long s = System.currentTimeMillis();
        try {

            super.paint(g);
            Color background = getBackground();
            if (background != null) {
                g.setColor(background);
                g.fillRect(0, 0, getWidth(), getHeight());
            }
            g.setColor(new Color(200, 20, 20));
            g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);

            drawLine(g);

            SwingUtil.antialiasingOn((Graphics2D) g);
            Frame frame = new Frame();
            frame.time = lastTime;
            display(frame);

            if (shapeSpace.size() == 0) {
                return;
            }

            synchronized (sortList) {
                for (int i = sortList.size() - 1; i >= 0; i--) {
                    Integer id = sortList.get(i);
                    Cache cache1 = cache.get(id);
                    T node = shapeSpace._findNode(id);

                    if (cache1 == null || !Objects.equals(node.getAngle(), cache1.getAngle())) {
                        updateCache(id);
                        cache1 = cache.get(id);
                    }

                    /* draw */
                    if (selectList.contains(node.getId())) {
                        g.drawImage(cache1.getSelectImage().getImage(), (int) node.getX(), (int) node.getY(), null);
                    } else {
                        if (mouseHoverId != null && mouseHoverId.equals(node.getId())) {
                            g.drawImage(cache1.getHoverImage().getImage(), (int) node.getX(), (int) node.getY(), null);
                        } else {
                            g.drawImage(cache1.getCanvasImage().getImage(), (int) node.getX(), (int) node.getY(), null);
                        }
                    }
                }
            }
        } finally {
            lastTime = System.currentTimeMillis() - s;
        }

        repaint();
    }

    private void drawLine(Graphics g) {
        final Color color = new Color(0, 0, 0, 50);
        int offset = 100;
        int x = offset;
        int y = offset;
        g.setColor(color);
        while (x < w) {
            g.drawLine(x, 0, x, h - 2);
            x += offset;
        }
        while (y < h) {
            g.drawLine(0, y, w - 2, y);
            y += offset;
        }
    }

    private void display(Frame frameObj) {
        if (displayListeners.size() == 0) {
            return;
        }
        displayListeners.forEach(e -> e.handler(frameObj));
    }

    private void updateCache(Integer id) {
        T t = shapeSpace._findNode(id);

        Polygon polygon = t.getPolygon();
        Polygon p1 = polygon.clone();
        p1.move(0, 0);

        Polygon p2 = p1.offsetOfCopy(-0.5);
        p2.move(0, 0);

        java.awt.Polygon polygon1 = p1.toAwtPolygon();
        java.awt.Polygon polygon2 = p2.toAwtPolygon();

        BufferedImage bufferedImage = commonImage(polygon1, polygon2);
        SwingUtil.CanvasImage canvasImage = SwingUtil.createCanvasImage(bufferedImage);

        BufferedImage bufferedImage1 = selectImage(polygon1, polygon2);
        SwingUtil.CanvasImage selectImage = SwingUtil.createCanvasImage(bufferedImage1);

        BufferedImage bufferedImage2 = hoverImage(polygon1, polygon2);
        SwingUtil.CanvasImage hoverImage = SwingUtil.createCanvasImage(bufferedImage2);

        Cache cache1 = new Cache();
        cache1.setAngle(t.getAngle());
        cache1.setCanvasImage(canvasImage);
        cache1.setSelectImage(selectImage);
        cache1.setHoverImage(hoverImage);
        cache.put(id, cache1);
    }

    private BufferedImage hoverImage(java.awt.Polygon p, java.awt.Polygon p2) {
        BufferedImage bufferedImage = getBufferedImage(p);
        Graphics g = getGraphics(bufferedImage);

        g.setColor(new Color(0, 0, 0, 0));
        g.fillRect(0, 0, p.getBounds().width, p.getBounds().height);
        g.setColor(new Color(84, 84, 84, 255));
        g.fillPolygon(p);
        g.setColor(new Color(78, 157, 231, 255));
        g.drawPolygon(p2);
        return bufferedImage;
    }

    private void mouseMoved(Point point) {
        if (mouseHoverId != null) {
            T t = shapeSpace._findNode(mouseHoverId);
            if (!t.getPolygon().contains(point.x, point.y)) {
                mouseHoverId = null;
            }
        }
        mouseHoverId = findNodeOfId(point);
    }

    public Node findNode(Point point) {
        synchronized (sortList) {
            for (Integer id : sortList) {
                T t = shapeSpace._findNode(id);
                if (t.getPolygon().contains(point.x, point.y)) {
                    return t;
                }
            }
        }
        return null;
    }

    public Integer findNodeOfId(Point point) {
        synchronized (sortList) {
            for (Integer id : sortList) {
                T t = shapeSpace._findNode(id);
                if (t.getPolygon().contains(point.x, point.y)) {
                    return t.getId();
                }
            }
        }
        return null;
    }

    private void mouseExited() {
        mouseHoverId = null;
    }

    private void leftPressed(Point point) {
        Integer nodeOfId = findNodeOfId(point);

        if (nodeOfId != null) {
            if (!selectList.contains(nodeOfId)) {
                selectList.add(nodeOfId);
                synchronized (sortList) {
                    sortList.remove(nodeOfId);
                    sortList.add(0, nodeOfId);
                }
            }
        }
    }

    private void rightPressed(Point point) {
        if (!leftCtrlPressed) {
            selectList.clear();
        }
        for (T node : shapeSpace._getNodes()) {
            if (node.getPolygon().contains(point.x, point.y)) {
                selectList.add(node.getId());
                break;
            }
        }
    }

    private void rightReleased(Point point) {

    }

    private void leftReleased(Point point) {

        if (leftCtrlPressed) {
            return;
        }
        Integer nodeOfId = findNodeOfId(point);
        if (nodeOfId == null) {
            selectList.clear();
        }
    }

    private void leftClicked(Point point) {

    }

    private void rightClick(Point point) {
        if (selectList.size() == 0) {
            return;
        }

        long count = selectList.stream().filter(e -> shapeSpace._findNode(e).getPolygon().contains(point.x, point.y)).count();
        if (count <= 0) {
            selectList.clear();
            return;
        }

        JPopupMenu popupMenu = new JPopupMenu();
        if (selectList.size() == 1) {
            JMenuItem edit = new JMenuItem("编辑坐标");
            edit.addActionListener(e -> {
                T node = shapeSpace.findNode(selectList.iterator().next());
                PolygonPointEditing polygonPointEditing = new PolygonPointEditing(node.getPolygon());
                JDialog jDialog = new JDialog();
                jDialog.setContentPane(polygonPointEditing);
                jDialog.setPreferredSize(new Dimension(1620, 940));
                jDialog.pack();
                SwingUtil.toCenter(jDialog);
                jDialog.setVisible(true);
            });
            popupMenu.add(edit);
        }
        popupMenu.show(this, point.x, point.y);
    }

    private BufferedImage selectImage(java.awt.Polygon p, java.awt.Polygon p2) {

        BufferedImage bufferedImage = getBufferedImage(p);
        Graphics g = getGraphics(bufferedImage);

        g.setColor(new Color(0, 0, 0, 0));
        g.fillRect(0, 0, p.getBounds().width, p.getBounds().height);
        g.setColor(new Color(78, 157, 231, 255));
        g.fillPolygon(p);
        g.setColor(new Color(8, 213, 32, 255));
        g.drawPolygon(p2);
        return bufferedImage;
    }

    private static BufferedImage getBufferedImage(java.awt.Polygon p) {
        return new BufferedImage(p.getBounds().width, p.getBounds().height, BufferedImage.TYPE_4BYTE_ABGR);
    }

    private static Graphics getGraphics(BufferedImage bufferedImage) {
        Graphics g = bufferedImage.getGraphics();
        SwingUtil.antialiasingOn((Graphics2D) g);
        return g;
    }

    private BufferedImage commonImage(java.awt.Polygon p, java.awt.Polygon p2) {

        BufferedImage bufferedImage = getBufferedImage(p);
        Graphics g = getGraphics(bufferedImage);

        g.setColor(new Color(0, 0, 0, 0));
        g.fillRect(0, 0, p.getBounds().width, p.getBounds().height);
        g.setColor(new Color(50, 50, 50, 255));
        g.fillPolygon(p);
        g.setColor(new Color(78, 157, 231, 255));
        g.drawPolygon(p2);
        return bufferedImage;
    }

    @Override
    protected void addImpl(Component comp, Object constraints, int index) {
    }

    private void _initListener() {
        this.addMouseListener(new MouseListener(this));
        this.addMouseMotionListener(new MouseMotionListener(this));
        DragController d1 = new DragController(this);
        d1.setStartListener(DragController.DRAG_TYPE_LEFT, point -> {
            if (selectList.size() == 0) {
                return point;
            }
            double x = selectList.stream().mapToDouble(e -> shapeSpace._findNode(e).getX()).min().orElse(0.0D);
            double y = selectList.stream().mapToDouble(e -> shapeSpace._findNode(e).getY()).min().orElse(0.0D);
            return new Point(point.x - (int) x, point.y - (int) y);
        });
        d1.addDragListener(DragController.DRAG_TYPE_LEFT, (s, e) -> {
            if (selectList.size() == 0) {
                return;
            }

            double minx = Double.MAX_VALUE;
            double miny = Double.MAX_VALUE;
            for (Integer integer : selectList) {
                T t = shapeSpace._findNode(integer);
                if (t.getX() < minx) {
                    minx = t.getX();
                }
                if (t.getY() < miny) {
                    miny = t.getY();
                }
            }

            double offsetX = e.x - minx - s.x;
            double offsetY = e.y - miny - s.y;
            for (Integer integer : selectList) {
                T t = shapeSpace._findNode(integer);
                t.translate(offsetX, offsetY);
            }
        });
    }

    @Override
    public void close() throws IOException {
    }

    static class Cache {
        private Double angle;
        private SwingUtil.CanvasImage canvasImage;
        private SwingUtil.CanvasImage selectImage;
        private SwingUtil.CanvasImage hoverImage;

        public SwingUtil.CanvasImage getSelectImage() {
            return selectImage;
        }

        public void setSelectImage(SwingUtil.CanvasImage select) {
            this.selectImage = select;
        }

        public Double getAngle() {
            return angle;
        }

        public void setAngle(Double angle) {
            this.angle = angle;
        }

        public SwingUtil.CanvasImage getCanvasImage() {
            return canvasImage;
        }

        public void setCanvasImage(SwingUtil.CanvasImage canvasImage) {
            this.canvasImage = canvasImage;
        }

        public SwingUtil.CanvasImage getHoverImage() {
            return hoverImage;
        }

        public void setHoverImage(SwingUtil.CanvasImage hoverImage) {
            this.hoverImage = hoverImage;
        }
    }

    public static class Frame {
        private long time; // 上一帧的消耗的时间

        public long getTime() {
            return time;
        }
    }

    public interface DisplayListener {
        void handler(Frame frameObj);
    }

    private static class MouseListener implements java.awt.event.MouseListener {

        private final Canvas2<?> canvas;

        public MouseListener(Canvas2<?> canvas) {
            this.canvas = canvas;
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            canvas.requestFocus();
            if (1 == e.getButton()) {
                canvas.leftClicked(e.getPoint());
            }
            if (3 == e.getButton()) {
                canvas.rightClick(e.getPoint());
            }
        }

        @Override
        public void mousePressed(MouseEvent e) {
            canvas.requestFocus();
            if (1 == e.getButton()) {
                canvas.leftPressed(e.getPoint());
            }
            if (3 == e.getButton()) {
                canvas.rightPressed(e.getPoint());
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            canvas.requestFocus();
            if (1 == e.getButton()) {
                canvas.leftReleased(e.getPoint());
            }
            if (3 == e.getButton()) {
                canvas.rightReleased(e.getPoint());
            }
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            canvas.requestFocus();
        }

        @Override
        public void mouseExited(MouseEvent e) {
            canvas.mouseExited();
        }
    }

    private static class MouseMotionListener implements java.awt.event.MouseMotionListener {

        private final Canvas2<?> canvas;

        public MouseMotionListener(Canvas2<?> canvas) {
            this.canvas = canvas;
        }

        @Override
        public void mouseDragged(MouseEvent e) {

        }

        @Override
        public void mouseMoved(MouseEvent e) {
            Point point = e.getPoint();
            canvas.mouseMoved(point);
        }
    }

}
